13.1 评论在数据库中的表示

从psots表到comments表是一对多关系,
从users表到comments表也是一对多关系。

1. 在app/models.py中定义Comment模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ...
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text)
body_html = db.Column(db.Text)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
disabled = db.Column(db.Boolean) # 协管员通过该字段查禁不当评论
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
post_is = db.Column(db.Integer, db.ForeignKey('posts.id'))
@staticmethod
def on_changed_body(target, value, oldvalue, initiator):
allowed_tags = ['a', 'abbr', 'acronym', 'b', 'code', 'em', 'i', 'strong']
# 将Markdown源文本转换为合乎需要的HTNL文本
target.body_html = bleach.linkify(bleach.clean(
markdown(value, output_format='html'),
tags=allowed_tags, strip=True))
db.event.listen(Comment.body, 'set', Comment.on_changed_body)

2. 在app/models.py中定义users表和posts表与comennts表的一对多关系:

1
2
3
4
5
6
7
8
9
10
# ...
class User(UserMixin, db.Model):
# ...
comments = db.relationship('Comment', backref='author', lazy='dynamic')
class Post(db.Model):
# ...
comments = db.relationship('Comment', backref='post', layz='dynamic')

13.2 显示评论框和提交评论

1. 在app/main/forms.py中定义评论表单:

1
2
3
4
5
# ...
class CommentForm(FlaskForm):
body = StringField('Enter your comment', validators=[DataRequired()])
submit = SubmitField('Submit')

2. 在app/main/views.py中定义在文章页显示评论的路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@main.route('/post/<int:id>', methods=['GET', 'POST'])
def post(id):
post = Post.query.get_or_404(id)
form = CommentForm()
if form.validate_on_submit():
comment = Comment(body=form.body.data,
post=post,
author=current_user._get_current_object())
db.session.add(comment)
db.session.commit()
flash('Your comment has been published.')
return redirect(url_for('main.post', id=post.id, page=-1))
page = request.args.get('page', 1, type=int)
if page == -1:
# 用总评论数除以每页显示数量再加1获得真正需要请求的页码,使得提交评论后能转到看到自己评论的页面
page = (post.comments.count() - 1) // current_app.config['FLASKY_COMMENTS_PER_PAGE'] + 1
pagination = post.comments.order_by(Comment.timestamp.asc()).paginate(
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'],
error_out=False)
comments = pagination.items
return render_template('post.html', posts=[post], form=form,
comments=comments, pagination=pagination)

注意

  • Post模型一样,评论的author字段不能直接设为current_user,因为这个变量是上下文代理对象,要使用current_user._get_current_object()获取真正的用户对象。
  • url_for()函数的page参数设为-1,用来请求最后一页,以达到提交评论后转到可以看到自己刚才发表的评论的页面。真正的页码通过(用总评论数除以每页显示数量)再加1获得。

3. 在app/templates/_post.html中添加到博客文章评论的链接:

1
2
3
4
5
6
7
# ...
<a href="{{ url_for('main.post', id=post.id)}}#comments">
<span class="label label-primary">
{{ post.comments.count() }} Comments
</span>
</a>

注意url_for()函数后面加了一个#comments后缀,这个后缀称为URL片段,其作用是用于指定加载页面后滚动条所在的初始位置。该例中,Web浏览器会寻找id为comments的元素并滚动页面。

13.3 管理评论

1. 在app/templates/base.html中在顶端导航条添加管理评论的链接:

1
2
3
4
5
# ...
{% if current_user.can(Permission.MODERATE_COMMENT) %}
<li><a href="{{ url_for('main.moderate') }}">Moderate</a></li>
{% endif %}
# ...

2. 在app/main/views.py中定义管理评论的路由(用于显示所有评论):

1
2
3
4
5
6
7
8
9
10
11
12
13
# ...
@main.route('/moderate')
@login_required
@permission_required(Permission.MODERATE_COMMENT)
def moderate():
page = request.args.get('page', 1, type=int)
pagination =Comment.query.order_by(Comment.timestamp.desc()).paginate(
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'],
error_out=False)
comments = pagination.items
return render_template('moderate.html', comments=comments,
pagination=pagination, page=page)

3. 在app/templates/moderate.html中定义管理评论的页面模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{% extends "base.html" %}
{% import "_macros.html" as macros %}
{% block title %}Flasky - Comment Moderation{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Comment Moderation</h1>
</div>
{% set moderate = True %}
{% include '_comments.html' %}
{% if pagination %}
<div class="pagination">
{{ macros.pagination_widget(pagination, '.moderate') }}
</div>
{% endif %}
{% endblock %}
  • 模板中通过Jinja2提供的set指令定义了一个模板变量moderate,并将其值设为True,这个变量的作用是决定在_comments.html模板中是否渲染管理评论功能。

4. 在app/templates/_comments.html中定义渲染评论内容的模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<ul class="comments">
{% for comment in comments %}
<li class="comment">
<div class="comment-thumbnail">
<a href="{{ url_for('main.user', username=comment.author.username) }}">
<img class="img-rounded profile-thumbnail" src="{{ comment.author.gravatar(size=40) }}">
</a>
</div>
<div class="comment-content">
<div class="comment-date">{{ moment(comment.timestamp).fromNow() }}</div>
<div class="comment-author"><a href="{{ url_for('main.user', username=comment.author.username) }}">{{ comment.author.username }}</a></div>
<div class="comment-body">
{% if comment.disabled %}
# 不当的评论用以下内容显示
<p><i>This comment has been disabled by a moderator.</i></p>
{% endif %}
{% if moderate or not comment.disabled %}
{% if comment.body_html %}
{{ comment.body_html | safe }}
{% else %}
{{ comment.body }}
{% endif %}
{% endif %}
</div>
{% if moderate %}
<br>
{% if comment.disabled %}
<a class="btn btn-default btn-xs" href="{{ url_for('main.moderate_enable', id=comment.id, page=page) }}">Enable</a>
{% else %}
<a class="btn btn-danger btn-xs" href="{{ url_for('main.moderate_disable', id=comment.id, page=page) }}">Disable</a>
{% endif %}
{% endif %}
</div>
</li>
{% endfor %}
</ul>

5. 在app/main/views.py中定义显示评论、禁用评论的路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 显示评论
@main.route('/moderate/enable/<int:id>') # 评论的id
@login_required
@permission_required(Permission.MODERATE_COMMENT)
def moderate_enable(id):
comment = Comment.query.get_or_404(id)
comment.disabled = False
db.session.add(comment)
return redirect(url_for('main.moderate', page=request.args.get('page', 1, type=int)))
# 禁用评论
@main.route('/moderate/disable/<int:id>') # 评论的id
@login_required
@permission_required(Permission.MODERATE_COMMENT)
def moderate_disable(id):
comment = Comment.query.get_or_404(id)
comment.disabled = True
db.session.add(comment)
return redirect(url_for('main.moderate', page=request.args.get('page', 1, type=int)))
  • redirect()函数中设置了page参数,如果之前的请求中指定了page参数,则会将其转入重定向中。